#pragma once

#include <iostream>
#include <string>
#include <unistd.h>
#include "../comm/util.hpp"
#include "../comm/log.hpp"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include<sys/time.h>
#include<sys/resource.h>

namespace ns_runner
{
    using namespace ns_util;
    using namespace ns_log;
    class Runner
    {
    public:
        Runner() {}
        ~Runner() {}

        static void SetProcLimit(int cpu_limit,int mem_limit)
        {
            //设置cpu限制
            struct rlimit _cpu_limit;
            _cpu_limit.rlim_max=RLIM_INFINITY; //硬条件无限制
            _cpu_limit.rlim_cur=cpu_limit; //软条件为我们自己设置的cpu运行时间
            setrlimit(RLIMIT_CPU,&_cpu_limit);
            
            //设置内存限制
            struct rlimit _mem_limit;
            _mem_limit.rlim_max=RLIM_INFINITY;
            _mem_limit.rlim_cur=mem_limit *1024; //基本单位是byte，所以我们给的mem_limit是多少，就是多少kb
            setrlimit(RLIMIT_AS,&_mem_limit);

        }



        static int Run(const std::string &filename,int cpu_limit,int mem_limit)
        {
            // 获取当前文件名对应的标准输入文件名
            std::string _stdin = PathUtil::Stdin(filename);
            // 获取当前文件名对应的标准输出文件名
            std::string _stdout = PathUtil::Stdout(filename);
            // 获取当前文件名对应的标准错误文件名
            std::string _stderr = PathUtil::Stderr(filename);

            // 创建当前可执行程序的标准输入文件
            umask(0);
            int infd = open(_stdin.c_str(), O_CREAT | O_WRONLY, 0644);
            int outfd = open(_stdout.c_str(), O_CREAT | O_WRONLY, 0644);
            int errfd = open(_stderr.c_str(), O_CREAT | O_WRONLY, 0644);
            



            if (infd < 0 || outfd < 0 || errfd < 0)
            {
                LOG(ERROR)<<"运行时打开文件错误!"<<std::endl;
                return -1; // 创建可执行程序对应的文件错误，返回-1
            }
            int pid = fork();
            if (pid < 0)
            {
                close(infd);
                close(outfd);
                close(errfd);
                LOG(ERROR)<<"运行时创建子进程出错!"<<std::endl;
                return -2; // 创建子进程失败，返回-2
            }
            else if (pid == 0)
            {
                // 子进程
                //子进程进行进程替换，运行对应的可执行程序
                //因为此时进程替换运行的使我们自己创建的可执行程序，所以必须使用l系列的exec函数，l系列包含可执行程序文件路径
                //如果如果不包含路径，操作系统无法找到对应的可执行程序，这个路径一般为相对路径
                //同时，虽然子进程进行了程序替换，但是本身的进程内核结构是不变的，继承的文件描述符依然是存在的

                //对子进程的标准输入/标准输出/标准错误三个文件描述符进行重定向,只影响子进程
                dup2(infd,0);
                dup2(outfd,1);
                dup2(errfd,2);
                
                //对子进程在运行可执行程序时消耗的cpu时长和占用的内存进行限制
                SetProcLimit(cpu_limit,mem_limit);

                execl(PathUtil::Exe(filename).c_str(),PathUtil::Exe(filename).c_str(),nullptr);
                LOG(ERROR)<<"子进程运行出错!"<<std::endl;
                exit(1);//如果替换失败，让子进程直接终止即可
            }
            else
            {
                // 父进程
                // 父进程不关心打开的文件，所以创建并打开的文件描述符关闭
                close(infd);
                close(outfd);
                close(errfd);
                int status = 0;           // 输出型参数，用于获取子进程的退出信息，如子进程退出码和信号，我们重点关心信号
                waitpid(pid, &status, 0); // 阻塞式等待
                LOG(INFO)<<"运行完毕,info: "<<(status & 0x7f)<<std::endl;
                return status & 0x7f;     // 获取信号，如果为0证明，子进程运行完毕；如果>0证明子进程接收到了信号，没有运行完毕
            }

        }
    };

}